/*
 * FindReplacePanel
 * Copyright (C) 2011, Aleksey Nikiforov
 *
 * This file is part of FindReplacePanel.
 *
 * FindReplacePanel is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FindReplacePanel is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package simplex3d.console.findreplace;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.util.ArrayDeque;
import java.util.ResourceBundle;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter.DefaultHighlightPainter;
import javax.swing.text.JTextComponent;
import javax.swing.undo.UndoableEdit;


/**
 *
 * @author Aleksey Nikiforov (lex)
 */
class WorkArea {


    private static class Entry {
        final int from;
        final int to;
        final UndoableEdit edit;

        public Entry(int from, int to, UndoableEdit edit) {
            this.from = from;
            this.to = to;
            this.edit = edit;
        }

        @Override
        public String toString() {
            return "Entry{" + "from=" + from + ", to=" + to + '}';
        }
    }


    private class HistoryManager {
        private JTextComponent textComponent;
        private final Action undoAction;
        private final Action redoAction;

        private Action undoForward;
        private Action redoForward;

        private ArrayDeque<Entry> undos;
        private ArrayDeque<Entry> redos;
        private Entry init;

        public void updateActions() {
            if (undoForward != null) undoAction.setEnabled(undoForward.isEnabled());
            if (redoForward != null) redoAction.setEnabled(redoForward.isEnabled());
        }
        
        public void init() {
            undos = new ArrayDeque<Entry>();
            redos = new ArrayDeque<Entry>();
            init = new Entry(from, to, null);
        }
        public void clear() {
            undos = null;
            redos = null;
        }

        private int outerUndoCount = 0;
        

        HistoryManager(JTextComponent textComponent) {
            this.textComponent = textComponent;


            this.textComponent.getDocument().addUndoableEditListener(new UndoableEditListener() {
                public void undoableEditHappened(UndoableEditEvent e) {
                    if (undos != null) {
                        redos.clear();
                        undos.push(new Entry(from, to, e.getEdit()));
                    }
                }
            });


            final ResourceBundle bundle = ResourceBundle.getBundle("simplex3d/console/findreplace/Bundle");

            final String keyUndo = "FindReplacePanel.undo";
            undoAction = new AbstractAction() {
                {
                    putValue(NAME, bundle.getString(keyUndo + ".text"));
                    putValue(MNEMONIC_KEY, (int) bundle.getString(keyUndo + ".mnemonic").charAt(0));
                    putValue(ACTION_COMMAND_KEY, FindReplacePanel.CMD_UNDO);
                }

                public void actionPerformed(ActionEvent e) {
                    textChangedOverride = true;
                    if (undoForward != null) undoForward.actionPerformed(e);
                    historyManger.updateActions();


                    if (undos != null) {

                        boolean innerAction = false;
                        while (!undos.isEmpty()) {
                            Entry entry = undos.pop();
                            if (entry.edit.canRedo()) {
                                redos.push(entry);
                                innerAction = true;
                            }
                            else {
                                undos.push(entry);
                                break;
                            }
                        }

                        if (outerAction) {
                            Entry entry;

                            if (innerAction) {
                                if (!undos.isEmpty()) entry = undos.peek(); else entry = init;
                            }
                            else {
                                outerUndoCount += 1;
                                entry = new Entry(0, 0, null);
                            }

                            from = entry.from;
                            to = entry.to;
                        }
                    }

                    updateHighlight();
                    textChangedAction.actionPerformed(null);
                    textChangedOverride = false;
                    outerAction = false;
                }
            };

            final String keyRedo = "FindReplacePanel.redo";
            redoAction = new AbstractAction() {
                {
                    putValue(NAME, bundle.getString(keyRedo + ".text"));
                    putValue(MNEMONIC_KEY, (int) bundle.getString(keyUndo + ".mnemonic").charAt(0));
                    putValue(ACTION_COMMAND_KEY, FindReplacePanel.CMD_REDO);
                }

                public void actionPerformed(ActionEvent e) {
                    textChangedOverride = true;
                    if (redoForward != null) redoForward.actionPerformed(e);
                    historyManger.updateActions();

                    
                    if (undos != null) {

                        boolean innerAction = false;
                        while (!redos.isEmpty()) {
                            Entry entry = redos.pop();
                            if (entry.edit.canUndo()) {
                                undos.push(entry);
                                innerAction = true;
                            }
                            else {
                                redos.push(entry);
                                break;
                            }
                        }

                        if (outerAction) {
                            Entry entry;

                            if (innerAction) {
                                entry = undos.peek();
                            }
                            else {
                                outerUndoCount -= 1;
                                if (outerUndoCount == 0) entry = init; else entry = new Entry(0, 0, null);
                            }

                            from = entry.from;
                            to = entry.to;
                        }
                    }

                    updateHighlight();
                    textChangedAction.actionPerformed(null);
                    textChangedOverride = false;
                    outerAction = false;
                }
            };
        }
    }


    private int from;
    private int to;
    private boolean interted;

    private boolean textChangedOverride = false;
    private final Action textChangedAction;

    private Color highlight;
    private DefaultHighlightPainter painter;

    private JTextComponent textComponent;
    private Object tag;

    private HistoryManager historyManger;
    private boolean outerAction = false;

    
    public WorkArea(JTextComponent textComponent, final Action textChanged) {
        this.textComponent = textComponent;
        textChangedAction = textChanged;
        historyManger = new HistoryManager(textComponent);
        setColor(FindReplacePanel.DEFAULT_WORK_AREA_COLOR);
        

        textComponent.getDocument().addDocumentListener(new DocumentListener() {
            public void insertUpdate(DocumentEvent e) {
                outerAction = true;
                if (textChangedOverride) return;

                historyManger.updateActions();

                if (from >= e.getOffset()) {
                    from = from + e.getLength();
                }
                if (to > e.getOffset()) {
                    to = to + e.getLength();
                }
                if (from >= to) {
                    from = 0;
                    to = 0;
                }
                updateHighlight();
                textChangedAction.actionPerformed(null);
            }
            public void removeUpdate(DocumentEvent e) {
                outerAction = true;
                if (textChangedOverride) return;

                historyManger.updateActions();

                if (from > e.getOffset()) {
                    int newStart = from - e.getLength();
                    if (newStart < e.getOffset()) newStart = e.getOffset();
                    from = newStart;
                }
                if (to > e.getOffset()) {
                    int newEnd = to - e.getLength();
                    if (newEnd < e.getOffset()) newEnd = e.getOffset();
                    to = newEnd;
                }
                if (from >= to) {
                    from = 0;
                    to = 0;
                }
                updateHighlight();
                textChangedAction.actionPerformed(null);
            }
            public void changedUpdate(DocumentEvent e) {
                // Do nothing.
            }
        });
    }

    
    private void updateHighlight() {
        try {
            if (tag == null && !isEmpty()) {
                tag = textComponent.getHighlighter().addHighlight(from, to, painter);
            }
            else {
                if (tag != null) textComponent.getHighlighter().removeHighlight(tag);
                if (!isEmpty()) {
                    tag = textComponent.getHighlighter().addHighlight(from, to, painter);
                }
            }
        } catch (BadLocationException ignore) {}
    }

    public void set(int from, int to) {
        if (from <= to) {
            this.from = from;
            this.to = to;
            this.interted = false;
        }
        else {
            this.from = to;
            this.to = from;
            this.interted = true;
        }

        if (from == 0 && to == textComponent.getDocument().getLength()) {
            from = 0;
            to = 0;
        }

        if (from != to) historyManger.init(); else historyManger.clear();
        updateHighlight();
    }

    public void setUndoForwards(Action undo, Action redo) {
        historyManger.undoForward = undo;
        if (undo != null) historyManger.undoAction.setEnabled(undo.isEnabled());

        historyManger.redoForward = redo;
        if (redo != null) historyManger.redoAction.setEnabled(redo.isEnabled());
    }


    public Action getUndoAction() {
        return historyManger.undoAction;
    }

    public Action getRedoAction() {
        return historyManger.redoAction;
    }

    public boolean isEmpty() {
        return (from == to);
    }
    
    public int getFrom() {
        return from;
    }

    public int getTo() {
        return to;
    }

    public boolean wasIntertedSelection() {
        return interted;
    }

    public Color getColor() {
        return highlight;
    }

    public final void setColor(Color c) {
        highlight = c;
        painter = new DefaultHighlightPainter(highlight);
    }
}
